﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SilverlightPlayground.Net;
using System.IO;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Threading;
using SilverlightPlayground.RFB.Readers;
using SilverlightPlayground.RFB.Security;
using System.Text;
using System.Diagnostics;
using System.Windows.Media.Imaging;

namespace SilverlightPlayground.RFB
{
    public class RfbClient : SocketClient
    {
        #region Static & Const

        private const byte ViewerRfbMajorVer = 3;
        private const byte ViewerRfbMinorVer = 4;

        public static readonly RfbEncoding[] SupportedEncodings = new RfbEncoding[] 
        {
            RfbEncoding.Raw,
            //RfbEncoding.CopyRect,
//            RfbEncoding.RRE,
            //RfbEncoding.CoRRE,
            //RfbEncoding.Hex,
            //RfbEncoding.NewFBSize
        }; 

        #endregion

        #region Events

        /// <summary>
        /// Occurs when [resize frame buffer].
        /// </summary>
        public event EventHandler ResizeFrameBuffer;
        /// <summary>
        /// Occurs when [frame buffer update].
        /// </summary>
        public event EventHandler<FrameBufferUpdateEventArgs> FrameBufferUpdate;
        /// <summary>
        /// Occurs when [failure].
        /// </summary>
        public event EventHandler<RfbFailureEventArgs> Failure;

        /// <summary>
        /// Occurs when [request credentials].
        /// </summary>
        public event EventHandler<RfbRequestCredentialsEventArgs> RequestCredentials;

        /// <summary>
        /// Called when [resize frame buffer].
        /// </summary>
        private void OnResizeFrameBuffer()
        {
            EventHandler handler = this.ResizeFrameBuffer;

            if (handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(
                    () => handler(this, EventArgs.Empty));
        }

        /// <summary>
        /// Called when [frame buffer update].
        /// </summary>
        /// <param name="rect">The rect.</param>
        /// <param name="colors">The colors.</param>
        private void OnFrameBufferUpdate(Rect rect, byte[] frame)
        {
            EventHandler<FrameBufferUpdateEventArgs> handler = this.FrameBufferUpdate;

            if (handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(
                    () => 
                    {
                        WriteableBitmap bitmap = new WriteableBitmap((int)rect.Width, (int)rect.Height);
                        bitmap.FromByteArray(frame);

                        handler(this, new FrameBufferUpdateEventArgs(rect, bitmap));
                    });
        }

        /// <summary>
        /// Called when [fail].
        /// </summary>
        /// <param name="error">The error.</param>
        protected override void OnFailure(Exception ex)
        {
            // TODO: decidere gestione errori
            EventHandler<RfbFailureEventArgs> handler = this.Failure;

            if (handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(
                () => handler(this, new RfbFailureEventArgs(ex)));

            base.OnFailure(ex);
        }

        /// <summary>
        /// Raises the <see cref="E:RequestCredentials"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SilverlightPlayground.RFB.Security.RfbRequestCredentialsEventArgs"/> instance containing the event data.</param>
        private void OnRequestCredentials(RfbRequestCredentialsEventArgs e)
        {
            EventHandler<RfbRequestCredentialsEventArgs> handler = this.RequestCredentials;

            if (handler != null)
                Deployment.Current.Dispatcher.BeginInvoke(
                    () => handler(this, e));
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether [share server].
        /// </summary>
        /// <value><c>true</c> if [share server]; otherwise, <c>false</c>.</value>
        public bool ShareServer { get; set; }
        /// <summary>
        /// Gets or sets the server.
        /// </summary>
        /// <value>The server.</value>
        public RfbServerInitialization Server { get; set; }
        /// <summary>
        /// Gets or sets the name of the server.
        /// </summary>
        /// <value>The name of the server.</value>
        public string ServerName { get; set; }
        /// <summary>
        /// Gets or sets the size of the pixel.
        /// </summary>
        /// <value>The size of the pixel.</value>
        public RfbPixelSize PixelSize { get; set; }
        /// <summary>
        /// Gets or sets the bytes per plane.
        /// </summary>
        /// <value>The bytes per plane.</value>
        public byte BytesPerPlane { get; private set; }
        /// <summary>
        /// Gets or sets the bit per pixel.
        /// </summary>
        /// <value>The bit per pixel.</value>
        public byte BitPerPixel { get; private set; }
        /// <summary>
        /// Gets or sets the depth.
        /// </summary>
        /// <value>The depth.</value>
        public byte Depth { get; private set; }
        /// <summary>
        /// Gets or sets a value indicating whether this instance is big endian.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is big endian; otherwise, <c>false</c>.
        /// </value>
        public bool IsBigEndian { get; private set; }
        /// <summary>
        /// Gets or sets a value indicating whether this instance is true color.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is true color; otherwise, <c>false</c>.
        /// </value>
        public bool IsTrueColor { get; private set; }
        /// <summary>
        /// Gets or sets the red max.
        /// </summary>
        /// <value>The red max.</value>
        public UInt16 RedMax { get; private set; }
        /// <summary>
        /// Gets or sets the green max.
        /// </summary>
        /// <value>The green max.</value>
        public UInt16 GreenMax { get; private set; }
        /// <summary>
        /// Gets or sets the blue max.
        /// </summary>
        /// <value>The blue max.</value>
        public UInt16 BlueMax { get; private set; }
        /// <summary>
        /// Gets or sets the red shift.
        /// </summary>
        /// <value>The red shift.</value>
        public byte RedShift { get; private set; }
        /// <summary>
        /// Gets or sets the green shift.
        /// </summary>
        /// <value>The green shift.</value>
        public byte GreenShift { get; private set; }
        /// <summary>
        /// Gets or sets the blue shift.
        /// </summary>
        /// <value>The blue shift.</value>
        public byte BlueShift { get; private set; }
        /// <summary>
        /// Gets or sets the server scaling.
        /// </summary>
        /// <value>The server scaling.</value>
        public RfbServerScaling ServerScaling { get; set; }
        /// <summary>
        /// Gets or sets the width of the current frame buffer.
        /// </summary>
        /// <value>The width of the current frame buffer.</value>
        public UInt16 CurrentFrameBufferWidth { get; set; }
        /// <summary>
        /// Gets or sets the height of the current frame buffer.
        /// </summary>
        /// <value>The height of the current frame buffer.</value>
        public UInt16 CurrentFrameBufferHeight { get; set; }

        #endregion

        #region Initialization

        /// <summary>
        /// Initializes a new instance of the <see cref="RfbClient"/> class.
        /// </summary>
        /// <param name="host">The host.</param>
        /// <param name="port">The port.</param>
        /// <param name="password">The password.</param>
        public RfbClient(string host, int port)
            : base(new DnsEndPoint(host, port))
        {
            // TODO: Togliere questo costruttore e usare quelli di SocketClient
#if TRACE
            Debug.WriteLine("RfbClient.ctor(host={0}, port={1})", host, port);
#endif

            this.ShareServer = true;
        } 

        #endregion

        #region Event Handlers

        /// <summary>
        /// Called when [connect].
        /// </summary>
        protected override void OnConnect()
        {
#if TRACE
            Debug.WriteLine("RfbClient.OnConnect()");
#endif

            this.BeginReceiveServerVersion();
            base.OnConnect();
        }

        /// <summary>
        /// Called when [receive].
        /// </summary>
        /// <param name="data"></param>
        protected override void OnReceive(object owner, byte[] data, string action, object state)
        {
#if TRACE
            Debug.WriteLine("RfbClient.OnReceive(data.Length={0},state={1})", data.Length, state);
#endif
            try
            {
                switch (action)
                {
                    case RfbAction.ReceivingServerVersion:
                        this.EndReceiveServerVersion(data);
                        break;
                    case RfbAction.ReceivingAuthenticationScheme:
                        this.EndReceiveAuthenticationScheme(data);
                        break;
                    case RfbAction.ReceivingVNCChallenge:
                        this.EndReceiveVNCChallenge(data);
                        break;
                    case RfbAction.ReceivingAuthenticationAnswer:
                        this.EndReceiveAuthenticationAnswer(data);
                        break;
                    case RfbAction.ReceivingServerInitialization:
                        this.EndReceiveServerInizialization(data);
                        break;
                    case RfbAction.ReceivingServerName:
                        this.EndReceiveServerName(data);
                        break;
                    case RfbAction.ReceivingUpdateType:
                        this.EndReceiveUpdateType(data);
                        break;
                    case RfbAction.ReceivingResizeFrameBuffer:
                        this.EndReceiveResizeFrameBuffer(data);
                        break;
                    case RfbAction.ReceivingScreenUpdate:
                        this.EndReceiveScreenUpdate(data);
                        break;
                    case RfbAction.ReceivingRectHeader:
                        this.EndReceiveRectHeader(data, (int)state);
                        break;
                    default:
                        if (owner is IProtocolReader)
                            this.OnChildReceive(data, (IProtocolReader)owner, action, state);
                        else
                            throw new InvalidOperationException("Unknown status");
                        break;
                }
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }

            base.OnReceive(owner, data, action, state);
        }

        /// <summary>
        /// Called when [sent].
        /// </summary>
        protected override void OnSent(object owner, string action, object state)
        {
#if TRACE
            Debug.WriteLine("RfbClient.OnSent()");
#endif

            switch (action)
            {
                case RfbAction.SentClientVersion:
                    this.BeginReceiveAuthenticationScheme();
                    break;
                case RfbAction.SentAuthentication:
                    this.BeginReceiveAuthenticationAnswer();
                    break;
                case RfbAction.SentClientInitialization:
                    this.BeginReceiveServerInizialization();
                    break;
                case RfbAction.SentPixelFormat:
                    this.SendSetEncodingsHeader();
                    break;
                case RfbAction.SentEncodingsHeader:
                    this.SendSetEncodingsBody();
                    break;
                case RfbAction.SentEncodingsBody:
                    if (this.ServerScaling != RfbServerScaling.Default)
                        this.SendScale();
                    else
                        this.SendUpdateRequest(0, 0, this.CurrentFrameBufferWidth, this.CurrentFrameBufferHeight, false);
                    break;
                case RfbAction.SentScale:
                    this.SendUpdateRequest(0, 0, this.CurrentFrameBufferWidth, this.CurrentFrameBufferHeight, false);
                    break;
                case RfbAction.SentUpdateRequest:
                    this.BeginReceiveUpdateType();
                    break;
                default:
                    if (owner is IProtocolReader)
                        this.OnChildSent((IProtocolReader)owner, action, state);
                    else
                        throw new InvalidOperationException("Unknown status");
                    break;
            }

            base.OnSent(owner, action, state);
        }

        #endregion

        #region Public Interface

        /// <summary>
        /// Begins the authentication.
        /// </summary>
        /// <param name="token">The token.</param>
        public void Login(RfbSecurityToken token)
        {
#if TRACE
            Debug.WriteLine("RfbClient.Login(token='{0}')", token);
#endif
            if (token is RfbVNCChallengeSecurityToken)
                this.SendVNCChallenge(token.Serialize());
            else
                throw new NotSupportedException("Token is not supported");
        }

        #endregion

        #region Send Methods

        /// <summary>
        /// Sends the client version.
        /// </summary>
        private void SendClientVersion()
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendClientVersion()");
#endif
            byte[] data = SocketUtilities.ToASCIIBytes(
                RfbProtoUtil.GetVerMsg(RfbClient.ViewerRfbMajorVer, RfbClient.ViewerRfbMinorVer));

            this.Send(data, RfbAction.SentClientVersion);
        }

        /// <summary>
        /// Sends the VNC challenge.
        /// </summary>
        /// <param name="data">The data.</param>
        private void SendVNCChallenge(byte[] data)
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendVNCChallenge(data.Length='{0}')", data.Length);
#endif
            this.Send(data, RfbAction.SentAuthentication);
        }

        /// <summary>
        /// Begins the client inizialization.
        /// </summary>
        private void SendClientInizialization()
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendClientInizialization()");
#endif
            byte[] initialization = RfbProtoUtil.GetCliInitMsg(this.ShareServer);
            this.Send(initialization, RfbAction.SentClientInitialization);
        }

        /// <summary>
        /// Begins the set pixel format.
        /// </summary>
        private void SendSetPixelFormat()
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendSetPixelFormat()");
#endif

            byte[] message;

            if (this.PixelSize == RfbPixelSize.Force8Bit)
            {
                BitPerPixel = 8; Depth = 8; IsBigEndian = false; IsTrueColor = true;
                RedMax = 7; GreenMax = 7; BlueMax = 3;
                RedShift = 0; GreenShift = 3; BlueShift = 6;
                BytesPerPlane = 1;
            }
            else if (this.PixelSize == RfbPixelSize.Force16Bit || !this.Server.IsTrueColor)
            {
                BitPerPixel = 16; Depth = 16; IsBigEndian = false; IsTrueColor = true;
                RedMax = 63; GreenMax = 31; BlueMax = 31;
                RedShift = 0; GreenShift = 6; BlueShift = 11;
                BytesPerPlane = 2;
            }
            else
            {
                BitPerPixel = this.Server.Bpp; Depth = this.Server.Depth; IsBigEndian = false; IsTrueColor = this.Server.IsTrueColor;
                RedMax = this.Server.RedMax; GreenMax = this.Server.GreenMax; BlueMax = this.Server.BlueMax;
                RedShift = this.Server.RedShift; GreenShift = this.Server.GreenShift; BlueShift = this.Server.BlueShift;
                BytesPerPlane = (byte)((BitPerPixel + 7) / 8);
            }

            message = RfbProtoUtil.GetSetPixelFormatMsg(
                this.BitPerPixel, this.Depth, this.IsBigEndian, this.IsTrueColor,
                this.RedMax, this.GreenMax, this.BlueMax,
                this.RedShift, this.GreenShift, this.BlueShift);

            this.Send(message, RfbAction.SentPixelFormat);
        }

        /// <summary>
        /// Sets the encodings.
        /// </summary>
        private void SendSetEncodingsHeader()
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendSetEncodingsHeader()");
#endif

            byte[] message = RfbProtoUtil.GetSetEncodingsMsgHdr((UInt16)RfbClient.SupportedEncodings.Length);
            this.Send(message, RfbAction.SentEncodingsHeader);
        }

        /// <summary>
        /// Sends the set encodings body.
        /// </summary>
        private void SendSetEncodingsBody()
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendSetEncodingsBody()");
#endif

            byte[] message = RfbProtoUtil.GetSetEncodingsMsg(RfbClient.SupportedEncodings);
            this.Send(message, RfbAction.SentEncodingsBody);
        }

        /// <summary>
        /// Begins the send scale.
        /// </summary>
        /// <param name="serverScaling">The server scaling.</param>
        private void SendScale()
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendScale()");
#endif

            byte[] message = RfbProtoUtil.GetSetScaleMsg(this.ServerScaling);
            this.Send(message, RfbAction.SentScale);
        }

        /// <summary>
        /// Begins the send update request.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="incremental">if set to <c>true</c> [incremental].</param>
        private void SendUpdateRequest(UInt16 x, UInt16 y, UInt16 width, UInt16 height, bool incremental)
        {
#if TRACE
            Debug.WriteLine("RfbClient.SendUpdateRequest(x={0},y={1},width={2},height={3},incr={4})", x, y, width, height, incremental);            
#endif
            byte[] message = RfbProtoUtil.GetFrameBufUpdReqMsg(x, y, width, height, incremental);
            this.Send(message, RfbAction.SentUpdateRequest);
        }

        #endregion

        #region Receive Methods

        /// <summary>
        /// Begins the read server version.
        /// </summary>
        private void BeginReceiveServerVersion()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveServerVersion()");
#endif
            this.Receive(RfbSize.VersionMessage, RfbAction.ReceivingServerVersion);
        }

        /// <summary>
        /// Handles the version.
        /// </summary>
        private void EndReceiveServerVersion(byte[] data)
        {
            string message = SocketUtilities.ToASCIIString(data);

#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveServerVersion(message='{0}')", message);
#endif

            try
            {
                if (!RfbProtoUtil.IsValidVerMsg(message))
                    throw new Exception("The server is not a VNC server!");

                int majorVer, minorVer;

                RfbProtoUtil.GetVerFromMsg(message, out majorVer, out minorVer);

                if (majorVer == 3 && minorVer < 3)
                    throw new Exception("This server version is not supported!");

                this.SendClientVersion();
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Begins the read authentication scheme.
        /// </summary>
        private void BeginReceiveAuthenticationScheme()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveAuthenticationScheme()");
#endif
            base.Receive(4, RfbAction.ReceivingAuthenticationScheme);
        }

        /// <summary>
        /// Begins the authentication.
        /// </summary>
        private void EndReceiveAuthenticationScheme(byte[] data)
        {
            RfbAuthenticationScheme scheme = (RfbAuthenticationScheme)SocketUtilities.ToU32(data);

#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveAuthenticationScheme(scheme='{0}')", scheme);
#endif

            try
            {
                switch (scheme)
                {
                    case RfbAuthenticationScheme.ConnectionFailed:
                        throw new SocketFailureException { ErrorCode = SocketError.AccessDenied };
                    case RfbAuthenticationScheme.NoAuthentication:
                        this.SendClientInizialization();
                        break;
                    case RfbAuthenticationScheme.VncAuthentication:
                        this.BeginReceiveVNCChallenge();
                        break;
                    default:
                        break;
                }
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Begins the VNC authentication.
        /// </summary>
        private void BeginReceiveVNCChallenge()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveVNCChallenge()");
#endif
            this.Receive(RfbSize.AuthenticationChallenge, RfbAction.ReceivingVNCChallenge);
        }

        /// <summary>
        /// Ends the receive VNC challenge.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveVNCChallenge(byte[] data)
        {
#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveVNCChallenge(data.Length='{0}')", data.Length);
#endif
            RfbRequestCredentialsEventArgs args = new RfbRequestCredentialsEventArgs();
            args.SecurityToken = new RfbVNCChallengeSecurityToken(data);
            this.OnRequestCredentials(args);
        }

        /// <summary>
        /// Begins the read authentication answer.
        /// </summary>
        private void BeginReceiveAuthenticationAnswer()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveAuthenticationAnswer()");
#endif
            this.Receive(sizeof(System.Int32), RfbAction.ReceivingAuthenticationAnswer);
        }

        /// <summary>
        /// Ends the receive authentication answer.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveAuthenticationAnswer(byte[] data)
        {
#if TRACE
            Debug.WriteLine("RfbClient.EndReadAuthenticationAnswer(data.Length='{0}')", data.Length);
#endif

            RfbAuthResult result = (RfbAuthResult)SocketUtilities.ToU32(data);

            try
            {
                switch (result)
                {
                    case RfbAuthResult.Ok:
                        this.SendClientInizialization();
                        break;
                    case RfbAuthResult.Failed:
                        throw new Exception("Authentication failed!");
                    case RfbAuthResult.TooMany:
                        throw new Exception("Too many!");
                    default:
                        throw new Exception("Authentication failed but reason unknown!");
                }
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Ends the client inizialization.
        /// </summary>
        private void BeginReceiveServerInizialization()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveServerInizialization()");
#endif
            this.Receive(RfbSize.ServerInitialization, RfbAction.ReceivingServerInitialization);
        }

        /// <summary>
        /// Ends the read server inizialization.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveServerInizialization(byte[] data)
        {
#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveServerInizialization(data.Length='{0}')", data.Length);
#endif

            try
            {
                this.Server = new RfbServerInitialization(data);
                this.CurrentFrameBufferWidth = this.Server.Width;
                this.CurrentFrameBufferHeight = this.Server.Height;
                this.BeginReceiveServerName();
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Ends the inizialization.
        /// </summary>
        /// <param name="serverInizialization">The server inizialization.</param>
        private void BeginReceiveServerName()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveServerName()");
#endif
            this.Receive(this.Server.NameLen, RfbAction.ReceivingServerName);
        }

        /// <summary>
        /// Ends the name of the read server.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveServerName(byte[] data)
        {
            try
            {
                this.ServerName = Encoding.UTF8.GetString(data, 0, data.Length);

#if TRACE
                Debug.WriteLine("RfbClient.EndReceiveServerName(name='{0}')", this.ServerName);
#endif

                this.SendSetPixelFormat();
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Begins the read updates.
        /// </summary>
        private void BeginReceiveUpdateType()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveUpdateType()"); 
#endif
            this.Receive(1, RfbAction.ReceivingUpdateType);
        }

        /// <summary>
        /// Ends the type of the receive update.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveUpdateType(byte[] data)
        {
            RfbServMsgType type = (RfbServMsgType)data[0];

#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveUpdateType(type={0})", type); 
#endif

            try
            {
                switch (type)
                {
                    case RfbServMsgType.FrameBufferUpdate:
                        this.BeginReceiveScreenUpdate();
                        break;
                    case RfbServMsgType.SetColorMapEntries:
                        throw new Exception("The server is sending SetColorMapEntries!");
                    case RfbServMsgType.Bell:
                        break;
                    case RfbServMsgType.ServerCutText:
                        // this.ReadServerCutText();
                        break;
                    case RfbServMsgType.ResizeFrameBuffer:
                        this.BeginReceiveResizeFrameBuffer();
                        break;
                    default:
                        throw new Exception("The server is sending unknown message!");
                }
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Begins the receive resize frame buffer.
        /// </summary>
        private void BeginReceiveResizeFrameBuffer()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveResizeFrameBuffer()");
#endif
            this.Receive(RfbSize.ResizeFrameBuffer - 1, RfbAction.ReceivingResizeFrameBuffer);
        }

        /// <summary>
        /// Ends the read resize frame buffer.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveResizeFrameBuffer(byte[] data)
        {
#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveResizeFrameBuffer(data.Length='{0}')", data.Length);
#endif

            try
            {
                ResizeFrameBufMsg resizeFrameBufMsg = new ResizeFrameBufMsg(data);

                this.CurrentFrameBufferWidth = resizeFrameBufMsg.Width;
                this.CurrentFrameBufferHeight = resizeFrameBufMsg.Height;
                this.OnResizeFrameBuffer();
                this.SendUpdateRequest(0, 0, this.CurrentFrameBufferWidth, this.CurrentFrameBufferHeight, true);
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Reads the screen update.
        /// </summary>
        private void BeginReceiveScreenUpdate()
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveScreenUpdate()"); 
#endif
            this.Receive(RfbSize.FrameBufferUpdate - 1, RfbAction.ReceivingScreenUpdate);
        }

        /// <summary>
        /// Ends the read screen update.
        /// </summary>
        /// <param name="data">The data.</param>
        private void EndReceiveScreenUpdate(byte[] data)
        {
#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveScreenUpdate(data.Length={0})", data.Length);
#endif

            try
            {
                int numRects = (int)RfbProtoUtil.GetNumRectsFromFrameBufUpdMsg(data);
                this.BeginReceiveRectHeader(numRects);
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Reads the rects.
        /// </summary>
        /// <param name="numRects">The num rects.</param>
        private void BeginReceiveRectHeader(int numRects)
        {
#if TRACE
            Debug.WriteLine("RfbClient.BeginReceiveRectHeader(numRects={0})", numRects); 
#endif
            this.Receive(RfbSize.FrameBufferUpdateRectangleHeader, RfbAction.ReceivingRectHeader, numRects);
        }

        /// <summary>
        /// Ends the read rect header.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="numRects">The num rects.</param>
        private void EndReceiveRectHeader(byte[] data, int numRects)
        {
#if TRACE
            Debug.WriteLine("RfbClient.EndReceiveRectHeader(data.Length={0}, numRects={1})", data.Length, numRects); 
#endif
            // TODO: togliere lambda
            try
            {
                FrameBufUpdRectMsgHdr frameBufUpdRectHdr = new FrameBufUpdRectMsgHdr(data);
                Rect rect = new Rect(frameBufUpdRectHdr.X, frameBufUpdRectHdr.Y, frameBufUpdRectHdr.Width, frameBufUpdRectHdr.Height);

                if (frameBufUpdRectHdr.Encoding == RfbEncoding.NewFBSize)
                {
                    this.CurrentFrameBufferWidth = (UInt16)rect.Width;
                    this.CurrentFrameBufferHeight = (UInt16)rect.Height;
                }
                else
                {
                    RfbRectReader reader = RfbRectReader.Create(this, frameBufUpdRectHdr.Encoding);

                    reader.RawUpdate += (s, e) => this.OnFrameBufferUpdate(e.Rectangle, e.Frame);

                    reader.Completed += (s, e) =>
                    {
                        if (e.Error != null)
                            this.OnFailure(e.Error);
                        else
                        {
                            if (--numRects > 0)
                                this.BeginReceiveRectHeader(numRects);
                            else
                                this.SendUpdateRequest(0, 0, this.CurrentFrameBufferWidth, this.CurrentFrameBufferHeight, true);
                        }
                    };

                    reader.Read(rect);
                }
            }
            catch (Exception ex)
            {
                this.OnFailure(ex);
            }
        }

        /// <summary>
        /// Ends the receive rect.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="status">The status.</param>
        private void OnChildReceive(byte[] data, IProtocolReader child, string action, object state)
        {
#if TRACE
            Debug.WriteLine("RfbClient.OnChildReceive(data.Length='{0}')", data.Length);
#endif
            child.OnReceive(data, action, state);
        }

        /// <summary>
        /// Called when [rect reader sent].
        /// </summary>
        /// <param name="status">The status.</param>
        private void OnChildSent(IProtocolReader child, string action, object state)
        {
#if TRACE
            Debug.WriteLine("RfbClient.OnChildSent()");
#endif
            child.OnSent(action, state);
        }

        #endregion

        #region Inner Classes
        
        public abstract class RfbRectReader : IProtocolReader
        {
            #region Events

            /// <summary>
            /// Occurs when [completed].
            /// </summary>
            public event EventHandler<ReadCompletedEventArgs> Completed;
            /// <summary>
            /// Occurs when [frame buffer update].
            /// </summary>
            internal event EventHandler<RawUpdateEventArgs> RawUpdate;

            /// <summary>
            /// Called when [completed].
            /// </summary>
            protected void OnCompleted()
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.OnCompleted()"); 
#endif

                EventHandler<ReadCompletedEventArgs> handler = this.Completed;

                if (handler != null)
                    handler(this, new ReadCompletedEventArgs());
            }

            /// <summary>
            /// Called when [fail].
            /// </summary>
            /// <param name="error">The error.</param>
            protected void OnFailure(Exception ex)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.OnCompleted(ex={0})", ex);
#endif

                EventHandler<ReadCompletedEventArgs> handler = this.Completed;

                if (handler != null)
                    handler(this, new ReadCompletedEventArgs(ex));
            }

            /// <summary>
            /// Receives the specified length.
            /// </summary>
            /// <param name="length">The length.</param>
            /// <param name="action">The action.</param>
            protected void Receive(int length, string action)
            {
                this.Receive(length, action, null);
            }

            /// <summary>
            /// Receives the specified length.
            /// </summary>
            /// <param name="length">The length.</param>
            /// <param name="status">The status.</param>
            protected void Receive(int length, string action, object state)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.Receive(length={0})", length); 
#endif
                this.RFB.Receive(this, length, action, state);
            }

            /// <summary>
            /// Sends the specified data.
            /// </summary>
            /// <param name="data">The data.</param>
            /// <param name="action">The action.</param>
            protected void Send(byte[] data, string action)
            {
                this.Send(data, action, null);
            }

            /// <summary>
            /// Sends the specified data.
            /// </summary>
            /// <param name="data">The data.</param>
            protected void Send(byte[] data, string action, object state)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.Send(data.Length.={0})", data.Length); 
#endif
                this.RFB.Send(this, data, action, state);
            }

            /// <summary>
            /// Called when [receive].
            /// </summary>
            /// <param name="data">The data.</param>
            public virtual void OnReceive(byte[] data, string action, object state)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.OnReceive()");
#endif
            }

            /// <summary>
            /// Called when [sent].
            /// </summary>
            public virtual void OnSent(string action, object state)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.OnSent()");
#endif
            }

            /// <summary>
            /// Called when [frame buffer update].
            /// </summary>
            /// <param name="rect">The rect.</param>
            /// <param name="colors">The colors.</param>
            protected void OnRawUpdate(Rect rect, byte[] frame)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.OnFrameBufferUpdate(rect={0}, frame.Length={1})", rect, frame.Length); 
#endif

                EventHandler<RawUpdateEventArgs> handler = this.RawUpdate;

                if (handler != null)
                    handler(this, new RawUpdateEventArgs(rect, frame));
            }

            #endregion

            /// <summary>
            /// Gets or sets the RFB.
            /// </summary>
            /// <value>The RFB.</value>
            protected RfbClient RFB { get; set; }

            /// <summary>
            /// Creates the specified encoding.
            /// </summary>
            /// <param name="encoding">The encoding.</param>
            /// <returns></returns>
            public static RfbRectReader Create(RfbClient rfb, RfbEncoding encoding)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.Create(rfb={0}, encoding={1})", rfb.ServerName, encoding);
#endif

                if (encoding == RfbEncoding.Raw)
                    return new RawRectReader(rfb);
                else if (encoding == RfbEncoding.RRE)
                    return new RRERectReader(rfb);

                //TODO: Add here other encodings

                throw new NotSupportedException("Encoding not supported");
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="RfbRectReader"/> class.
            /// </summary>
            /// <param name="rfb">The RFB.</param>
            public RfbRectReader(RfbClient rfb)
            {
#if TRACE
                Debug.WriteLine("RfbRectReader.Create(rfb={0})", rfb.ServerName);
#endif

                this.RFB = rfb;
            }

            /// <summary>
            /// Reads the specified rect.
            /// </summary>
            /// <param name="rect">The rect.</param>
            /// <param name="completed">The completed.</param>
            /// <param name="fail">The fail.</param>
            public abstract void Read(Rect rect);

            /// <summary>
            /// Gets the color from data.
            /// </summary>
            /// <param name="data">The data.</param>
            /// <param name="offset">The offset.</param>
            /// <returns></returns>
            protected uint GetColorFromData(byte[] data, UInt32 offset)
            {
                // TODO: Rimuovere da qui!
                return RfbProtoUtil.GetPixelFromData(data, offset, this.RFB.BytesPerPlane);
            }
        }
        
        #endregion

        internal interface IProtocolReader
        {
            /// <summary>
            /// Called when [receive].
            /// </summary>
            /// <param name="data">The data.</param>
            void OnReceive(byte[] data, string action, object state);
            /// <summary>
            /// Called when [sent].
            /// </summary>
            void OnSent(string action, object state);            
        }
    }
}
